package main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Scanner;

import codeg.CodeGeneration;
import codeg.CodeGenerationContext;
import codeg.Codeg;
import codeg.CodegException;
import codegManager.CodegManager;
import codegManager.ProjectSource;


/*
 * This is just a fake compiler for Cyan. It expects any text containing codegs and generates Java code.
 */
public class CyanCompiler {
    public static void compile(final File path) {
        Codeg codeg = null;
        Hashtable<String, Codeg> codegs = new Hashtable<String, Codeg>();
        Hashtable<String, CodeGeneration> codes = new Hashtable<String, CodeGeneration>();
        
        CodegManager codegManager = new CodegManager(
            CyanCompilerActivator.getBundleContext(), new ProjectSource() {
                public File getProjectFolder() {
                    return path;
                }
            });

        // Create a directory to output the compiled files
        File outputDir = new File(path, "compiled");
        if (!outputDir.exists()) outputDir.mkdir();

        // Find all files in the project folder.
        for (File file : path.listFiles()) {
            codegs.clear();
            if (!file.isFile() || !file.getName().endsWith(".cy")
                || !file.canRead()) continue;
            String fname = file.getName().split("\\.")[0];

            // Create the output file
            File ofile = new File(outputDir, fname + ".java");
            CyanWriter fw = null;
            try {
                fw = new CyanWriter(ofile);
            }
            catch (IOException e) {}

            // Read the input file
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(file));
            }
            catch (FileNotFoundException e) {}

            char[] input = new char[(int) file.length()];

            try {
                br.read(input);
            }
            catch (IOException e) {}

            // Get all codeg objects declared in this file
            Scanner s;
            s = new Scanner(new String(input));
            String decl = s.findWithinHorizon(CodegManager.codegPattern, 0);
            while (decl != null) {
                try {
                    codeg = codegManager.getCodeg(decl);
                }
                catch (CodegException e) {
                    System.err.println("Error loading codeg: " + decl);
                    decl = s.findWithinHorizon(CodegManager.codegPattern, 0);
                    continue;
                }
                codegs.put(decl, codeg);
                decl = s.findWithinHorizon(CodegManager.codegPattern, 0);
            }
            s.close();

            // Generate code for all codegs
            CodeGeneration codeGeneration = null;
            LinkedList<Codeg> codegsList = new LinkedList<Codeg>(codegs.values());
            for (String declaration : codegs.keySet()) {
                codeg = codegs.get(declaration);
                try {
                    codeGeneration = codeg.getCode(new CodeGenerationContext(CodeGenerationContext.LANGUAGE.JAVA, codegsList));
                }
                catch (CodegException e) {
                    System.err.println("Error generating code for: " + declaration + ": " + e.getMessage());
                }
                if (codeGeneration == null) continue;
                codes.put(declaration, codeGeneration);
            }
            
            // Output imports code
            LinkedList<String> imports = new LinkedList<String>();
            for (CodeGeneration cg : codes.values()) {
                if (cg.getImports() == null) continue;
                for (String pi : cg.getImports()) {
                    if (!imports.contains(pi)) {
                        imports.add(pi);
                        fw.write("import " + pi + ";");
                    }
                }
            }

            // Output global scope code
            // We generate global code for each codeg class for which there is
            // an object.
            // A compiler may choose any other way of dealing with this
            // problem.
            // It could get global code based on codeg roles, for example.
            LinkedList<String> classes = new LinkedList<String>();
            for (CodeGeneration cg : codes.values()) {
                String class_ = cg.getCodeg().getClass().getName();
                if (!classes.contains(class_)) {
                    if (cg.getGlobalCode() == null) continue;
                    fw.write(cg.getGlobalCode());
                    classes.add(class_);
                }
            }

            // Output local scope code
            fw.write("class " + fname + " {");
            fw.write("\tpublic static void main(String[] args) {");
            CodeGeneration cg;
            String output = new String(input);
            for (String key : codes.keySet()) {
                cg = codes.get(key);
                if (cg.getLocalCode() == null) continue;
                output = output.replace(key, cg.getLocalCode());
            }
            fw.write(output);
            fw.write("\t}\n}");
            fw.close();
            
            codegs.clear();
            codes.clear();
        }
    }
}

/**
 * A FileWriter that ignores exceptions for clearer code. Yep, bad practice.
 * That's why it's hidden at the end of the file.
 */
class CyanWriter extends FileWriter {
    public CyanWriter(File file) throws IOException {
        super(file);
    }

    @Override
    public void write(String str) {
        try {
            super.write(str + "\n");
        }
        catch (IOException e) {}
    }

    @Override
    public void close() {
        try {
            super.close();
        }
        catch (IOException e) {}
    }
};